package com.lin.csdn.utils;

import com.lin.csdn.model.po.PoolState;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

public class PoolDataSource implements DataSource{

    private static Map<String, Driver> registerDrivers = new ConcurrentHashMap<>();
    private String driver ="com.mysql.cj.jdbc.Driver";
    private String url = "jdbc:mysql://localhost:3306/csdn?characterEncoding=UTF-8";
    private String username = "root";
    private String password = "0411,lzh";

    private final PoolState state = new PoolState();

    private int poolMaxActiveConnections = 200;
    private int poolMaxIdleConnections = 100;
    private int poolKeepAliveTime = 200;
    private int poolTimeToWait = 200;

    protected void pushConnection(PoolConnection conn) throws SQLException {
        synchronized (state) {
            state.getActiveConnections().remove(conn);
            if (state.getIdleConnections().size() < poolMaxIdleConnections) {
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                state.getIdleConnections().add(conn);
                state.notifyAll();
            } else {
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                conn.getRealConnection().close();
            }
        }
    }

    private PoolConnection popConnection() throws SQLException {
        PoolConnection conn = null;

        while (conn == null) {
            synchronized (state) {
                if (!state.getIdleConnections().isEmpty()) {
                    conn = state.getIdleConnections().remove(0);
                } else {
                    if (state.getActiveConnections().size() < poolMaxActiveConnections) {
                        conn = new PoolConnection(getRealConnection(), this);
                    } else {
                        PoolConnection oldConn = state.getActiveConnections().get(0);
                        long keepAliveTime = oldConn.getKeepAliveTime();
                        if (keepAliveTime > poolKeepAliveTime) {
                            state.getActiveConnections().remove(oldConn);
                            if (!oldConn.getRealConnection().getAutoCommit()) {
                                oldConn.getRealConnection().rollback();
                            }
                            conn = new PoolConnection(oldConn.getRealConnection(),this);
                        } else {
                            try {
                                state.wait(poolTimeToWait);
                            } catch (InterruptedException e) {
                                break;
                            }
                        }
                    }
                }

                if (conn != null) {
                    if (!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }
                    conn.setKeepAliveTime(System.currentTimeMillis());
                    state.getActiveConnections().add(conn);
                }
            }
        }

        if (conn == null) {
            throw new SQLException("PoolDataSource get connection fail.");
        }
        return conn;
    }

    private Connection getRealConnection() throws SQLException {
        return getRealConnection(username, password);
    }

    private Connection getRealConnection(String username, String password) throws SQLException {
        Properties props = new Properties();
        if (username != null) {
            props.put("user", username);
        }
        if (password != null) {
            props.put("password", password);
        }
        return getRealConnection(props);
    }

    private Connection getRealConnection(Properties properties) throws SQLException {
        initializeDriver();
        Connection conn = DriverManager.getConnection(url, properties);
        return conn;
    }

    private void initializeDriver() throws SQLException {
        if (!registerDrivers.containsKey(driver)) {
            try {
                Class.forName(driver);
            } catch (Exception e) {
                throw new SQLException("register Driver fail. Cause：" + e);
            }
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        return popConnection().getProxyConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return popConnection().getProxyConnection();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}